// generated 2005/8/1 13:00:51 CST by xihels@163.com
//


#include "config.h"
#include "MsgBox.h"
#include <gtkmm/textbuffer.h>
#include <gdkmm/pixbuf.h>
#include "pixmaps.h"
#include "QQ.h"
extern CodeConverter g2u;
extern CodeConverter u2g;

MsgBox::MsgBox() 
{
    set_pixels_below_lines(3);
    set_left_margin(3);
    set_wrap_mode(Gtk::WRAP_WORD);

    Glib::RefPtr<Gtk::TextBuffer> buffer =
	Gtk::TextBuffer::create(taglist.getTagTable());

    set_buffer(buffer);
    show_all();
}

void MsgBox::clear()
{
    Glib::RefPtr<Gtk::TextBuffer> buffer = get_buffer();
    buffer->set_text("");
}




void MsgBox::showTitle(const string& sender, RefTag titlecolor, uint32_t time)
{
    struct tm *tm;
    tm = localtime(reinterpret_cast<time_t*>(&time));
    char timetext[255];
    snprintf(timetext, 32, "(%04d-%02d-%02d %02d:%02d:%02d) ",
    				tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
				tm->tm_hour, tm->tm_min, tm->tm_sec);
    //printf(datetext);
    Glib::RefPtr<Gtk::TextBuffer> buffer = get_buffer();
    
    Gtk::TextBuffer::iterator end = buffer->end();
    Glib::RefPtr<Gtk::TextBuffer::Mark> mark = buffer->create_mark(end);

    end = buffer->insert_with_tag(end, Glib::ustring(timetext), taglist.getTimeFont());
    Gtk::TextBuffer::iterator first = mark->get_iter();
    buffer->apply_tag(titlecolor, first, end);
    
    buffer->move_mark(mark, end);

    end = buffer->insert_with_tag(end, sender, taglist.getBoldFont());
    end = buffer->insert_with_tag(end, ": ", taglist.getBoldFont());
    first = mark->get_iter();
    buffer->apply_tag(titlecolor, first, end);

    buffer->delete_mark(mark);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void MsgBox::showMessage(const string& message)
{
    /**
     * 消息中可能内嵌一些图片信息，除了普通的文本之外，图片的信息格式为：
     * 一. 缺省表情，缺省表情的前导字节是0x14，0x14之后的一个字节表示缺省表情的索引值
     * 二. 自定义表情，自定义表情的前导字节是0x15，0x15之后的格式为:
     * 	  1. 存在性字节，如果这个表情第一次出现，
     * 	  则为0x33，如果已经出现过，则为0x34，当为0x33时，后面的内容是
     * 		 i.   未知的一个字节
     *       ii.  表情图片的文件名，其文件名由md5的字符串形式和扩展名构成，
     *       因此这个长度应该是32 + 1 + 3(一般是GIF)
     *       iii. 表情的shortcut长度，以'A'为基准，如果长度是2，则这个字节是'C'
     *       iv.  表情的shortcut
     *    2. 如果为0x34时，则后面的内容为：
     * 		 i.   1字节索引值，假如这个自定义表情出现在第一个位置，则这个字节为'A'   
     *    3. 如果为0x36时
     * 		 i. 自定义表情协议块的长度的10进制字符串形式，3字节
     * 		 ，不足者前部填为空格，比如为了表示这个自定义表情用了
     *          88个字节，那么这个字段就是" 88"，呵呵，晕吧，
     *          注意这个长度是从0x15开始算起，一直到结束。注意这个长度
     *          是字节长度，所以如果有中文在里面，这个字段靠不住，这个给我们带来了一些麻烦
     *       ii. 表情标识，1字节，标识这个表情是新的，还是已经出现过的，
     *       如果是新的，用'e'表示。如果是已经出现过的，
     *           用一个大写字母表示，第一个新表情代号是A，第二个是B，以此类推
     *       iii. 表情的快捷键字节长度，1字节，用一个大写字母表示，比如A表示长度为0，一次类推
     *       iv. 后面的内容开始一直到agent key之前的内容的长度，2字节，用16进制的字符串表示
     *       v. session id的16进制字符串形式，8字节，不足者前面是空格
     *       vi. 中转服务器IP的16进制字符串形式，注意是little-endian，
     *       那么ipv4的话自然就是8个字节了
     *       vii. 中转服务器端口号的16进制字符串形式，8个字节
     *       viii. file agent key，16字节
     *       ix. 图片的文件名，文件名的形式是MD5的字符串形式加上点加上后缀名而成，
     *       所以一般是36个字节，但是
     *           我想最好还是根据前面的长度减去其他字段的长度来判断好些
     *       x.  快捷键，长度前面已经说了
     *       xi. 一个字节，'A'，可能是用来分界用的
     *    4. 如果为0x37时
     *       0x37表示这个表情已经在前面出现过，参见0x36时的格式，
     *       0x37缺少0x36的iv, v, vii, viii, ix部分，
     *       其他部分均相同

*/

    // 消息正文，0是分隔符
    //     先不停的读，直到找到0x0为止
    //     要注意的是Gaim 0.64的QQ插件在这里不以0结尾，对于这种情况，我们需要处理
    //     不然Gaim 0.64发来的消息会解析错误 

    string::const_iterator first = message.begin();
    string::const_iterator last  = message.end();
    string::const_iterator iter = first;
    while (first < last){
	iter = find_if(first, last, isSmile());	// 查找有没有 表情符号或者 自定义表情
	if (iter == last) {			// 没有找到，直接显示
	    showMessage(first, last);
	    return;
	} else if (iter != first ) {		// 显示表情符号前面的消息
	    showMessage(first, iter);
	}

	if (QQ_DEFAULT_FACE_TAG == *iter) {		// 缺省表情
	    iter++;
	    int index = (unsigned char)(*iter);
	    showSmiley(index - 'A');		// 显示表情
	    iter++;
	} else {
	    iter = getCustomSmil(++iter);	// 自定义表情
	    showSimpleText("[贴图]");
	}
	first = iter;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void MsgBox::showMessage(string::const_iterator first, string::const_iterator last)
{
    const char* begin = &(*first);
    const char* end   = &(*last);
    string text=g2u.convert(begin, end - begin);
    showSimpleText(text);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
string::const_iterator MsgBox::getCustomSmil(string::const_iterator index)
{
    char ch = *index++;
    switch (ch) {
	case QQ_CUSTOM_FACE_EXIST:	// 如果为0x34时，表示这个表情已经出现过
	    //fprintf(stderr, "face = %c\n", *index);
	    index++;
	    break;
	case QQ_CUSTOM_FACE_NEW:
	    {	// 这个表情第一次出现
		index++;    // 一个未知字节
		// 32位md5码，一个'.', 一个扩展名'GIF'
		string filename(index, index + 32 + 1 + 3);
		index += filename.size();
		// shortcut长度
		int scutlen = *index++ - 'A';
		string shortcut(index, index + scutlen);
		index += shortcut.size();

		//fprintf(stderr, "%s, %s\n", filename.c_str(), shortcut.c_str());
		//index += (*index + 2 + 32 + 1);
		//index += (*index - 'A' + 1);
	    }
	    break;
	case QQ_CUSTOM_FACE_NEW_CLUSTER:    // 如果为0x36时
	    {
		char strlen[4];		    // 块长度
		memcpy(strlen, &(*index), 3);
		strlen[3] = 0;
		int len = atoi(strlen);
		index += 3;			   
		
		//  表情标识，1字节，标识这个表情是新的，
		//  还是已经出现过的
		char type = *index++;	    					    
		
		// shortcut长度
		int scutlen = *index++ - 'A';

		// 后面的内容开始一直到agent key之前的内容的长度，2字节，用16进制的字符串表示
		index += 2;

		// session id的16进制字符串形式，8字节，不足者前面是空格
		string sessionId(index, index + 8);
		index += 8;

		// 中转服务器IP的16进制字符串形式，
		// 注意是little-endian，那么ipv4的话自然就是8个字节了
		string severip(index, index + 8);
		index += 8;

		// 中转服务器端口号的16进制字符串形式，8个字节
		string severport(index, index + 8);
		index += 8;

		// file agent key，16字节
		string agentkey(index, index + 16);
		index += 16;

		// 图片的文件名，文件名的形式是MD5的字符串形式加上点加上后缀名而成.
		int filenamelen = len - 3 - 3 - 1 - 1 - scutlen - 2 - 8 - 8 - 8 - 16;
		string filename(index, index + filenamelen);
		index += filenamelen;
		//fprintf(stderr, "type = %c, filenamelen = %d, %s\n",
		//      type, filenamelen, filename.c_str());

		// 快捷键
		string shortcut(index, index + scutlen);
		index += shortcut.size();

		//fprintf(stderr, "scutlen = %d, shortcut = %s\n", scutlen, shortcut.c_str());
		
		index++;    //  一个字节，'A'，可能是用来分界用的
		
	    }
	    break;
	case QQ_CUSTOM_FACE_EXIST_CLUSTER:  
	    {
		char strlen[4];		    // 块长度
		memcpy(strlen, &(*index), 3);
		strlen[3] = 0;
		int len = atoi(strlen);
		index += 3;			   
		
		//  表情标识，1字节，标识这个表情是新的，
		//  还是已经出现过的
		char type = *index++;	    					    
		
		// shortcut长度
		int scutlen = *index++ - 'A';
		
		// 快捷键
		string shortcut(index, index + scutlen);
		index += shortcut.size();
		
		index++;    //  一个字节，'A'，可能是用来分界用的

		//fprintf(stderr, "type = %c\n", type);
	    }
	default:
	    fprintf(stderr, "unknow smiley = %02x\n", ch);
    }
    return index;
}

void MsgBox::showSimpleText(const string & text)
{
    Glib::RefPtr<Gtk::TextBuffer> buffer = get_buffer();
    Gtk::TextBuffer::iterator end = buffer->end();
    Gdk::Rectangle rect;
    get_visible_rect(rect);

    int y = -1;
    int height = -1;
    get_line_yrange(end, y, height);
    
    buffer->place_cursor(buffer->insert(end,text)); 
    
    if (y < rect.get_y() + rect.get_height() + 16)// 最后一行可见，也就是用户没有向上滚动
	scroll_to_mark(buffer->get_insert(), 0);// 插入文本后也要向下滚动，使最后一行继续可见
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*void MsgBox::showMessage(const string& text)
{
    Glib::RefPtr<Gtk::TextBuffer> buffer = get_buffer();
    Gtk::TextBuffer::iterator end = buffer->end();
    Gdk::Rectangle rect;
    get_visible_rect(rect);

    int y = -1;
    int height = -1;
    get_line_yrange(end, y, height);
    
    buffer->place_cursor(buffer->insert(end,text)); 
    
    if (y < rect.get_y() + rect.get_height() + 16)// 最后一行可见，也就是用户没有向上滚动
	scroll_to_mark(buffer->get_insert(), 0);// 插入文本后也要向下滚动，使最后一行继续可见
    
}*/

void MsgBox::insertPixbuf(const Glib::RefPtr<Gdk::Pixbuf>& pixbuf)
{
    Glib::RefPtr<Gtk::TextBuffer> buffer = get_buffer();
    buffer->insert_pixbuf(buffer->get_insert()->get_iter(), pixbuf);

    //Glib::RefPtr<Gtk::TextBuffer::Mark> mark = buffer->create_mark(end);
    //scroll_to_mark(mark, 0);

}
void MsgBox::insertImage(Gtk::Image* image)
{
    g_assert(image);
    Glib::RefPtr<Gtk::TextBuffer> buffer = get_buffer();
    Glib::RefPtr<Gtk::TextChildAnchor> anchor =
	buffer->create_child_anchor(buffer->end());

    add_child_at_anchor(*image, anchor);

    //Glib::RefPtr<Gtk::TextBuffer::Mark> mark = buffer->create_mark(buffer->end());
    //textBox->scroll_to_mark(mark, 0);

    image->show();

}

void MsgBox::getText(Glib::ustring& text) 
{
    Glib::RefPtr<Gtk::TextBuffer> buffer = get_buffer();
    // text.resize((buffer->size() + 2) * 2)

    Gtk::TextBuffer::iterator first = buffer->begin();
    Gtk::TextBuffer::iterator last  = buffer->end();
    string* data = NULL;
    Glib::RefPtr<Gtk::TextChildAnchor> anchor;
    gunichar c;
    for (; first != last; ++first) {
	c = first.get_char();
	if (0xFFFC == c) {
	    anchor = first.get_child_anchor();
	    if (anchor) {
		data = (string*)anchor->get_data("shortcut");
		anchor->set_data("shortcur", NULL);
		if (NULL != data) {
		    text.append(*data);
		    delete data;
		}
	    }
	}
	else 
	    text.push_back(c);
    }

}

void MsgBox::insertSmiley(int index, bool isend)
{
    Gtk::Image* image = getSmil(index);
    g_assert(image);

    string* data = new string(SmileyMap::getSmilCode(index));

    Glib::RefPtr<Gtk::TextBuffer> buffer = get_buffer();
    Gtk::TextBuffer::iterator iter =
       	isend ? buffer->end() : 
	buffer->get_insert()->get_iter();
    
    Glib::RefPtr<Gtk::TextChildAnchor> anchor = 
	buffer->create_child_anchor(iter);
    anchor->set_data("shortcut", data);
    
    add_child_at_anchor(*image, anchor);
    image->show();
}
